SPDX-FileCopyrightText: 2019 Julia Barnoin & Guillaume Charinet SPDX-FileCopyrightText: 2024 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
The Art of the Fugue # Authors: [Julia & Guillaume] # Date: [28.11.2019] # Blender version: [2.8] # OS: [Windows 10] #
from random import random, choice
from mathutils import Vector, Matrix
from mathutils.noise import random, seed_set
import bpy, bmesh
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False, confirm=False)
context = bpy.context
scene = context.sceneMaking new materials:
Red = bpy.data.materials.new("Red")
Red.diffuse_color = (0.8, 0, 0.000869932, 1)
Blue = bpy.data.materials.new("Blue")
Blue.diffuse_color = (0.113655, 0.510074, 0.8, 1)
Green = bpy.data.materials.new("Green")
Green.diffuse_color = (0.165985, 0.8, 0.0555711, 1)will apply the choosen material to the generated geometry
def apply_mat(mat):    obj = bpy.context.active_object
    obj.data.materials.append(mat)use of the golden ratio as proportion ratio for scaling and placing the geometry
def fib(a):    fibonaci = []
    x = 0
    y = 1
    while x < a:print(x)
        fibonaci.append(x)
        x, y = y, x + yprint (fibonaci)
    return fibonaci[4:]will create a rectangle with locations and dimensions data
def boite(Lx, Ly, Lz, Sx, Sy, Sz):    bpy.ops.mesh.primitive_cube_add(size=1, location=(Lx, Ly, Lz))
    bpy.context.object.dimensions = (Sx, Sy, Sz)
    return bpy.context.selected_objects[0]def get_BoundBox(ob):returns the corners of the bounding box of an object in world coordinates
    from mathutils import Vector
    bpy.context.view_layer.update()
    bbox_corners = [ob.matrix_world @ Vector(corner) for corner in ob.bound_box]
    return bbox_cornersdef check_Collision(box1, box2):Check Collision of 2 Bounding Boxes box1 & box2 must be lists of Vectors, containg the edges of the bounding boxes
print(‘COLLISION CHECK’) print(‘\n[START] Check collision with:’)
    x_max = max([e[0] for e in box1])
    x_min = min([e[0] for e in box1])
    y_max = max([e[1] for e in box1])
    y_min = min([e[1] for e in box1])
    z_max = max([e[2] for e in box1])
    z_min = min([e[2] for e in box1])print(‘[INFO] Box1 min %.2f, %.2f, %.2f’ % (x_min, y_min, z_min)) print(‘[INFO] Box1 max %.2f, %.2f, %.2f’ % (x_max, y_max, z_max))
    x_max2 = max([e[0] for e in box2])
    x_min2 = min([e[0] for e in box2])
    y_max2 = max([e[1] for e in box2])
    y_min2 = min([e[1] for e in box2])
    z_max2 = max([e[2] for e in box2])
    z_min2 = min([e[2] for e in box2])print(‘[INFO] Box2 min %.2f, %.2f, %.2f’ % (x_min2, y_min2, z_min2)) print(‘[INFO] Box2 max %.2f, %.2f, %.2f’ % (x_max2, y_max2, z_max2))
Check max and min values of coordinates Here, values must be inferior or equal
    isColliding = (
        (
            (x_max >= x_min2 and x_max <= x_max2)
            or (x_min <= x_max2 and x_min >= x_min2)
            or (x_max2 <= x_max and x_min2 >= x_min)
            or (x_max <= x_max2 and x_min >= x_min2)
        )
        and (
            (y_max >= y_min2 and y_max <= y_max2)
            or (y_min <= y_max2 and y_min >= y_min2)
            or (y_max2 <= y_max and y_min2 >= y_min)
            or (y_max <= y_max2 and y_min >= y_min2)
        )
        and (
            (z_max >= z_min2 and z_max <= z_max2)
            or (z_min <= z_max2 and z_min >= z_min2)
            or (z_max2 <= z_max and z_min2 >= z_min)
            or (z_max <= z_max2 and z_min >= z_min2)
        )
    )if isColliding: print(‘[RESULT] Collision found!’, box1.name, ‘and’, box2.name) else: print(‘[RESULT] No collision found.’)
print(‘[END]\n’)
    return isCollidingwill return collision check as True
def testCollision(a, b):    collision = check_Collision(get_BoundBox(a), get_BoundBox(b))
    if collision:
        print("[RESULT] Collision found!", a.name, "and", b.name)
    return collisionwill delete geometry
def deleteBoite():    bpy.ops.object.delete(use_global=False, confirm=False)will list all geometry that already exist
def list_all():    res = []
    existing_objects = list(bpy.data.objects)
    for obj in existing_objects:
        res.append(obj)
    return ressubject generation on the X axis
def make_subject_1():    fibonaci = fib(100)  #
    fibonaciX = fib(
        20
    )  #  Use of the fibonacci function for the localisation and dimmension data    Lx = 0
    res = []
    while Lx <= 100:  # Start of the main loop
        Ly = choice(fibonaci)  #
        Lz = choice(fibonaci)  #
        Sx = choice(
            fibonaciX
        )  # -------->  pick a random number in the fibonacci sequence
        Sy = choice(fibonaci)  #
        Sz = choice(fibonaci)  #
        if (
            Lx == 0 or Lx > 50 and Lx < 60 or Lx > 70 and Lx < 80
        ):  # condition to generate attachement points
            if (
                Ly + Sy < 89 and Lz + Sz < 89
            ):  # condition to restrain geometry to our 100x100 cube
                Sx = 21  # ---------> restrainng size of our attachement points
                Sy >= 21  #
                newboite = boite(Lx + (Sx / 2), Ly, Lz, Sx, Sy, Sz)
                apply_mat(Red)
                if not Lx == 0 and not testCollision(
                    oldboite, newboite
                ):  # checking collision so all are lincked by one of their faces
                    deleteBoite()
                else:
                    oldboite = newboite
                    res.append(oldboite)
                    Lx = Lx + Sx  # count will advance as the next geometry localisation
                    print(Lx)
        else:
            if (
                Ly + Sy < 89 and Lz + Sz < 89
            ):  # condition to generate in between attachement points
                Sx < 13  # ---------> restrainng size of our "notes"
                newboite = boite(Lx + (Sx / 2), Ly, Lz, Sx, Sy, Sz)
                apply_mat(Red)
                if not Lx == 0 and not testCollision(
                    oldboite, newboite
                ):  # checking collision so all are lincked by one of their faces
                    deleteBoite()
                else:
                    oldboite = newboite
                    res.append(oldboite)
                    Lx = Lx + Sx  # count will advance as the next geometry localisation
                    print(Lx)
    return resdef make_inversion(
    list, Axis, Sx, Sy, Sz
):  # making an inversion of the previous generated subject so they can be intricated
    res = []
    bpy.ops.object.select_all(action="DESELECT")
    for obj in list:
        obj.select_set(state=True)
    bpy.ops.object.duplicate_move()
    trans = bpy.ops.transform.rotate
    trans(value=3.14159, orient_axis=Axis)
    bpy.ops.transform.resize(
        value=(Sx, Sy, Sz)
    )  # making a mirror version of our subject
    for obj in bpy.context.selected_objects:
        res.append(obj)
    return resdef place_inversion(
    list, inversion, x, y, z
):  # placing our inversion so it cannot collide with the existent geometry
    bpy.ops.object.select_all(action="DESELECT")
    count = 0
    collision = True
    print(list)
    while collision:  # Starting of the loop
        count += 1
        print("loop number", count)
        collision = False
        for C1 in list:  #
            for (
                C2
            ) in (
                inversion
            ):  # ---------------> comparing existent geometry to the inversion
                print("test:", C1.name, C2.name)
                while testCollision(C1, C2):  # checking collision
                    bpy.ops.object.select_all(action="DESELECT")
                    for obj in inversion:
                        obj.select_set(state=True)
                    bpy.ops.transform.translate(
                        value=(x, y, z)
                    )  # if collision append the whole inversion will move again and again
                    collision = True
                print(collision)
                if collision:
                    break
            if collision:
                break
    print("number of test loops", count, collision)def make_voice4(list):  # generates voice 4 as subject 1 inversion
    inversion = make_inversion(list, "Z", 1, 1, -1)
    bpy.ops.transform.translate(
        value=(0, 50, 0),
        orient_type="GLOBAL",
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        orient_matrix_type="GLOBAL",
        constraint_axis=(True, False, False),
        mirror=True,
        use_proportional_edit=False,
        proportional_edit_falloff="SMOOTH",
        proportional_size=1,
        use_proportional_connected=False,
        use_proportional_projected=False,
    )
    place_inversion(list, inversion, 0, 0.5, 0.5)def make_subject_2():  # subject generation on the Y axis
    existing_objects = list(bpy.data.objects)
    fibonaci = fib(100)  #
    fibonaciX = fib(
        20
    )  #  Use of the fibonacci function for the localisation and dimmension data    Ly = 0
    res = []
    while Ly <= 100:  # Start of the main loop
        Lx = choice(fibonaci)  #
        Lz = choice(fibonaci)  #
        Sx = choice(
            fibonaci
        )  # -------->  pick a random number in the fibonacci sequence
        Sy = choice(fibonaciX)  #
        Sz = choice(fibonaci)  #
        if (
            Ly == 0 or Ly > 50 and Ly < 60 or Ly > 70 and Ly < 80
        ):  # condition to generate attachement points
            if (
                Lx + Sx < 89 and Lz + Sz < 89
            ):  # condition to restrain geometry to our 100x100 cube
                Sy = 21  # ---------> restrainng size of our attachement points
                newboite = boite(Lx, Ly + (Sy / 2), Lz, Sx, Sy, Sz)
                apply_mat(Blue)
                if Ly == 0:  # exeption for the starting attachement point
                    print("LOOP 1st CUBE")
                    collision = True
                    while (
                        collision
                    ):  # loop checking collision so new subject will not collide with other existing geometry
                        collision = False
                        for obj in existing_objects:
                            print("test:", newboite, obj)
                            if testCollision(newboite, obj):
                                collision = True
                                deleteBoite()
                                break
                            print(collision)
                        if collision:
                            break
                    if not collision:
                        Ly = (
                            Ly + Sy
                        )  # count will advance as the next geometry localisation
                        oldboite = newboite
                        res.append(oldboite)
                        print(Ly)
                else:  # generating our others attachement points
                    print("LOOP 2")
                    if not testCollision(
                        oldboite, newboite
                    ):  # checking collision so all are lincked by one of their faces
                        deleteBoite()
                    else:
                        collision = True
                        while (
                            collision
                        ):  # loop checking collision so new subject will not collide with other existing geometry
                            collision = False
                            for obj in existing_objects:
                                print("test:", newboite, obj)
                                if testCollision(newboite, obj):
                                    collision = True
                                    deleteBoite()
                                    break
                                print(collision)
                            if collision:
                                break
                        if not collision:
                            Ly = (
                                Ly + Sy
                            )  # count will advance as the next geometry localisation
                            oldboite = newboite
                            res.append(oldboite)
                            print(Ly)
        else:
            print("LOOP 3")
            if (
                Lx + Sx < 89 and Lz + Sz < 89
            ):  # condition to generate in between attachement points
                newboite = boite(Lx, Ly + (Sy / 2), Lz, Sx, Sy, Sz)
                apply_mat(Blue)
                if not testCollision(
                    oldboite, newboite
                ):  # checking collision so all are lincked by one of their faces
                    deleteBoite()
                else:
                    collision = True
                    while (
                        collision
                    ):  # loop checking collision so new subject will not collide with other existing geometry
                        collision = False
                        for obj in existing_objects:
                            print("test:", newboite, obj)
                            if testCollision(newboite, obj):
                                collision = True
                                deleteBoite()
                                break
                            print(collision)
                        if collision:
                            break
                    if not collision:
                        Ly = (
                            Ly + Sy
                        )  # count will advance as the next geometry localisation
                        oldboite = newboite
                        res.append(oldboite)
                        print(Ly)break
    return resdef make_voice3(list):  # generates voice 3 as subject 2 inversion
    voices_2_4_1 = list_all()
    inversion_2 = make_inversion(list, "Z", 1, 1, -1)
    bpy.ops.transform.translate(
        value=(75, 0, 0),
        orient_type="GLOBAL",
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        orient_matrix_type="GLOBAL",
        constraint_axis=(True, False, False),
        mirror=True,
        use_proportional_edit=False,
        proportional_edit_falloff="SMOOTH",
        proportional_size=1,
        use_proportional_connected=False,
        use_proportional_projected=False,
    )resizing as a augmentation in music theory
    bpy.ops.transform.resize(
        value=(0.80, 0.80, 0.80),
        orient_type="GLOBAL",
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        orient_matrix_type="GLOBAL",
        mirror=True,
        use_proportional_edit=False,
        proportional_edit_falloff="SMOOTH",
        proportional_size=1,
        use_proportional_connected=False,
        use_proportional_projected=False,
    )
    place_inversion(voices_2_4_1, inversion_2, 0.1, -0.1, 0.3)def make_subject_3():  # subject generation on the X axis
    existing_objects = list(bpy.data.objects)
    fibonaci = fib(100)  #
    fibonaciX = fib(
        20
    )  #  Use of the fibonacci function for the localisation and dimmension data    Lx = 0
    res = []
    while Lx <= 100:  # Start of the main loop
        Ly = choice(fibonaci)  #
        Lz = choice(fibonaci)  #
        Sx = choice(
            fibonaciX
        )  # -------->  pick a random number in the fibonacci sequence
        Sy = choice(fibonaci)  #
        Sz = choice(fibonaci)  #
        if (
            Lx == 0 or Lx > 50 and Lx < 60 or Lx > 70 and Lx < 80
        ):  # condition to generate attachement points
            if (
                Ly + Sy < 89 and Lz + Sz < 89
            ):  # condition to restrain geometry to our 100x100 cube
                Sx = 21  #
                Sz >= 21  # ---------> restrainng size of our attachement points
                newboite = boite(Lx + (Sx / 2), Ly, Lz, Sx, Sy, Sz)
                apply_mat(Green)
                if Lx == 0:  # exeption for the starting attachement point
                    print("LOOP 1st CUBE")
                    collision = True
                    while (
                        collision
                    ):  # loop checking collision so new subject will not collide with other existing geometry
                        collision = False
                        for obj in existing_objects:
                            print("test:", newboite, obj)
                            if testCollision(newboite, obj):
                                collision = True
                                deleteBoite()
                                break
                            print(collision)
                        if collision:
                            break
                    if not collision:
                        Lx = (
                            Lx + Sx
                        )  # count will advance as the next geometry localisation
                        oldboite = newboite
                        res.append(oldboite)
                        print(Lx)break
                else:  # generating our others attachement points
                    print("LOOP 2")
                    if not testCollision(
                        oldboite, newboite
                    ):  # checking collision so all are lincked by one of their faces
                        deleteBoite()
                    else:  # loop checking collision so new subject will not collide with other existing geometry
                        collision = True
                        while collision:
                            collision = False
                            for obj in existing_objects:
                                print("test:", newboite, obj)
                                if testCollision(newboite, obj):
                                    collision = True
                                    deleteBoite()
                                    break
                                print(collision)
                            if collision:
                                break
                        if not collision:
                            Lx = (
                                Lx + Sx
                            )  # count will advance as the next geometry localisation
                            oldboite = newboite
                            res.append(oldboite)
                            print(Lx)
        else:
            print("LOOP 3")
            if (
                Ly + Sy < 89 and Lz + Sz < 89
            ):  # condition to generate in between attachement points
                newboite = boite(Lx + (Sx / 2), Ly, Lz, Sx, Sy, Sz)
                apply_mat(Green)
                if not testCollision(
                    oldboite, newboite
                ):  # checking collision so all are lincked by one of their faces
                    deleteBoite()
                else:  # loop checking collision so new subject will not collide with other existing geometry
                    collision = True
                    while collision:
                        collision = False
                        for obj in existing_objects:
                            print("test:", newboite, obj)
                            if testCollision(newboite, obj):
                                collision = True
                                deleteBoite()
                                break
                            print(collision)
                        if collision:
                            break
                    if not collision:
                        Lx = (
                            Lx + Sx
                        )  # count will advance as the next geometry localisation
                        oldboite = newboite
                        res.append(oldboite)
                        print(Lx)
    return resdef make_voice6(list):  # generates voice 6 as subject 3 inversion
    voices_2_4_1 = list_all()
    inversion_2 = make_inversion(list, "Z", 1, 1, -1)
    bpy.ops.transform.translate(
        value=(0, 75, 0),
        orient_type="GLOBAL",
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        orient_matrix_type="GLOBAL",
        constraint_axis=(True, False, False),
        mirror=True,
        use_proportional_edit=False,
        proportional_edit_falloff="SMOOTH",
        proportional_size=1,
        use_proportional_connected=False,
        use_proportional_projected=False,
    )
    place_inversion(voices_2_4_1, inversion_2, 0.1, -0.1, 0.3)Calling all our subject and response
subject1 = make_subject_1()
make_voice4(subject1)
subject2 = make_subject_2()
make_voice3(subject2)
subject3 = make_subject_3()
make_voice6(subject3)